Apprenez à implémenter une stratégie robuste de gestion d'erreurs React avec des arbres de Error Boundaries pour une dégradation gracieuse et une expérience utilisateur améliorée.
Arbre de Périphériques de Gestion d'Erreurs React : Gestion Hiérarchique des Erreurs pour des Applications Robustes
L'architecture de React, basée sur les composants, favorise la réutilisabilité et la maintenabilité, mais elle introduit également le risque que les erreurs se propagent et perturbent l'ensemble de l'application. Les erreurs non gérées peuvent entraîner une expérience déroutante pour les utilisateurs, en affichant des messages cryptiques ou même en faisant planter l'application. Les périphériques de gestion d'erreurs (Error Boundaries) fournissent un mécanisme pour intercepter les erreurs JavaScript n'importe où dans leur arborescence de composants enfants, consigner ces erreurs et afficher une interface utilisateur de secours à la place de l'arborescence de composants qui a planté. Un arbre de périphériques de gestion d'erreurs bien conçu vous permet d'isoler les défaillances et d'offrir une meilleure expérience utilisateur en dégradant gracieusement des sections spécifiques de votre application sans affecter les autres.
Comprendre les Périphériques de Gestion d'Erreurs React
Introduits dans React 16, les périphériques de gestion d'erreurs sont des composants React qui interceptent les erreurs JavaScript n'importe où dans leur arborescence de composants enfants, consignent ces erreurs et affichent une interface utilisateur de secours à la place de l'arborescence de composants qui a planté. Les périphériques de gestion d'erreurs interceptent les erreurs lors du rendu, dans les méthodes de cycle de vie et dans les constructeurs de toute l'arborescence en dessous d'eux. Fait important, ils n'interceptent *pas* les erreurs pour :
- Les gestionnaires d'événements (plus d'informations ci-dessous)
- Le code asynchrone (par ex., les rappels de
setTimeoutourequestAnimationFrame) - Le rendu côté serveur
- Les erreurs levées dans le périphérique de gestion d'erreurs lui-même (plutôt que dans ses enfants)
Un composant de classe devient un périphérique de gestion d'erreurs s'il définit l'une (ou les deux) de ces méthodes de cycle de vie :
static getDerivedStateFromError(): Cette méthode est invoquée après qu'une erreur a été levée par un composant descendant. Elle reçoit l'erreur qui a été levée comme argument et doit retourner une valeur pour mettre à jour l'état.componentDidCatch(): Cette méthode est invoquée après qu'une erreur a été levée par un composant descendant. Elle reçoit deux arguments :error: L'erreur qui a été levée.info: Un objet contenant des informations sur le composant qui a levé l'erreur.
Exemple Simple de Périphérique de Gestion d'Erreurs
Voici un composant de base de périphérique de gestion d'erreurs :
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Met à jour l'état pour que le prochain rendu affiche l'interface de secours.
return { hasError: true };
}
componentDidCatch(error, info) {
// Vous pouvez également consigner l'erreur dans un service de rapport d'erreurs
console.error("Une erreur a été interceptée : ", error, info.componentStack);
//consignerErreurDansMonService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
// Vous pouvez afficher n'importe quelle interface de secours personnalisée
return <h1>Quelque chose s'est mal passé.</h1>;
}
return this.props.children;
}
}
Utilisation :
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
La Puissance de l'Arbre de Périphériques de Gestion d'Erreurs
Alors qu'un seul périphérique de gestion d'erreurs peut protéger toute votre application, une approche plus sophistiquée consiste à créer un *Arbre* de Périphériques de Gestion d'Erreurs. Cela signifie placer stratégiquement plusieurs périphériques de gestion d'erreurs à différents niveaux de votre hiérarchie de composants. Cela vous permet de :
- Isoler les Défaillances : Une défaillance dans une partie de l'application n'entraînera pas nécessairement la chute de toute l'interface utilisateur. Seule la partie encapsulée par le périphérique de gestion d'erreurs spécifique affichera l'interface de secours.
- Fournir des Interfaces de Secours Spécifiques au Contexte : Différentes parties de votre application peuvent nécessiter différentes interfaces de secours. Par exemple, un composant d'image défaillant pourrait afficher une image de substitution, tandis qu'un composant de récupération de données défaillant pourrait afficher un bouton "Réessayer".
- Améliorer l'Expérience Utilisateur : En plaçant soigneusement les périphériques de gestion d'erreurs, vous pouvez vous assurer que votre application se dégrade gracieusement, minimisant les perturbations pour l'utilisateur.
Construire un Arbre de Périphériques de Gestion d'Erreurs : Un Exemple Pratique
Considérons une application web affichant un profil utilisateur. Le profil se compose de plusieurs sections :
- Informations sur l'utilisateur (nom, lieu, biographie)
- Photo de profil
- Fil d'activité récent
- Liste des abonnés
Nous pouvons encapsuler chacune de ces sections avec son propre périphérique de gestion d'erreurs.
// ErrorBoundary.js (Le composant générique ErrorBoundary vu ci-dessus)
import ErrorBoundary from './ErrorBoundary';
function UserProfile() {
return (
<div>
<ErrorBoundary>
<UserInfo />
</ErrorBoundary>
<ErrorBoundary fallbackUI={<img src="/placeholder.png" alt="Image de substitution"/>}>
<ProfilePicture />
</ErrorBoundary>
<ErrorBoundary fallbackUI={<p>Échec du chargement de l'activité. Veuillez réessayer plus tard.</p>}>
<ActivityFeed />
</ErrorBoundary>
<ErrorBoundary fallbackUI={<p>Impossible de charger les abonnés.</p>}>
<FollowersList />
</ErrorBoundary>
</div>
);
}
Dans cet exemple, si le composant ProfilePicture ne parvient pas à se charger (par exemple, en raison d'une URL d'image cassée), seule la zone de la photo de profil affichera l'interface de secours (l'image de substitution). Le reste du profil restera fonctionnel. De même, une défaillance dans le composant ActivityFeed n'affectera que cette section, affichant un message "Veuillez réessayer plus tard".
Notez l'utilisation de la prop fallbackUI dans certains des composants ErrorBoundary. Cela nous permet de personnaliser l'interface de secours pour chaque section, offrant une expérience plus contextuelle et conviviale.
Techniques Avancées de Périphériques de Gestion d'Erreurs
1. Personnalisation de l'Interface de Secours
L'interface de secours par défaut (par exemple, un simple message "Quelque chose s'est mal passé") peut ne pas être suffisante pour tous les scénarios. Vous pouvez personnaliser l'interface de secours pour fournir des messages plus informatifs, proposer des actions alternatives ou même tenter de récupérer de l'erreur.
Comme montré dans l'exemple précédent, vous pouvez utiliser des props pour passer une interface de secours personnalisée au composant ErrorBoundary :
<ErrorBoundary fallbackUI={<CustomFallbackComponent />}>
<MyComponent />
</ErrorBoundary>
Le CustomFallbackComponent peut afficher un message d'erreur plus spécifique, suggérer des étapes de dépannage ou offrir un bouton "Réessayer".
2. Consignation des Erreurs vers des Services Externes
Bien que les périphériques de gestion d'erreurs empêchent les plantages de l'application, il est crucial de consigner les erreurs afin de pouvoir identifier et corriger les problèmes sous-jacents. La méthode componentDidCatch est l'endroit idéal pour consigner les erreurs vers des services externes de suivi des erreurs comme Sentry, Bugsnag ou Rollbar.
class ErrorBoundary extends React.Component {
// ...
componentDidCatch(error, info) {
// Consigner l'erreur dans un service de rapport d'erreurs
consignerErreurDansMonService(error, info.componentStack);
}
// ...
}
Assurez-vous de configurer votre service de suivi des erreurs pour gérer les erreurs JavaScript et vous fournir des informations détaillées sur l'erreur, y compris la trace de la pile des composants.
Exemple avec Sentry :
import * as Sentry from "@sentry/react";
import { BrowserTracing } from "@sentry/tracing";
Sentry.init({
dsn: "YOUR_SENTRY_DSN",
integrations: [new BrowserTracing()],
// Réglez tracesSampleRate sur 1.0 pour capturer 100 %
// des transactions pour le suivi des performances.
// Nous recommandons d'ajuster cette valeur en production
tracesSampleRate: 1.0,
});
class ErrorBoundary extends React.Component {
// ...
componentDidCatch(error, info) {
Sentry.captureException(error, { extra: info });
}
// ...
}
3. Périphériques de Gestion d'Erreurs et Gestionnaires d'Événements
Comme mentionné précédemment, les périphériques de gestion d'erreurs n'interceptent *pas* les erreurs à l'intérieur des gestionnaires d'événements. C'est parce que les gestionnaires d'événements sont exécutés de manière asynchrone, en dehors du cycle de vie de rendu de React. Pour gérer les erreurs dans les gestionnaires d'événements, vous devez utiliser un bloc try...catch.
function MyComponent() {
const handleClick = () => {
try {
// Code susceptible de lever une erreur
throw new Error("Quelque chose s'est mal passé dans le gestionnaire d'événements !");
} catch (error) {
console.error("Erreur dans le gestionnaire d'événements :", error);
// Afficher un message d'erreur Ă l'utilisateur
alert("Une erreur est survenue. Veuillez réessayer.");
}
};
return <button onClick={handleClick}>Cliquez-moi</button>;
}
4. Périphériques de Gestion d'Erreurs et Opérations Asynchrones
De même, les périphériques de gestion d'erreurs n'interceptent pas les erreurs dans les opérations asynchrones comme setTimeout, setInterval ou les Promises. Vous devez utiliser des blocs try...catch à l'intérieur de ces opérations asynchrones pour gérer les erreurs.
Exemple avec les Promises :
function MyComponent() {
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`Erreur HTTP ! statut : ${response.status}`);
}
const data = await response.json();
// Traiter les données
console.log(data);
} catch (error) {
console.error("Erreur lors de la récupération des données :", error);
// Afficher un message d'erreur Ă l'utilisateur
alert("Échec de la récupération des données. Veuillez vérifier votre connexion.");
}
};
fetchData();
}, []);
return <div>Chargement des données...</div>;
}
5. Réessayer les Opérations Échouées
Dans certains cas, il peut être possible de réessayer automatiquement une opération qui a échoué. Par exemple, si une requête réseau échoue en raison d'un problème de connectivité temporaire, vous pourriez implémenter un mécanisme de nouvelle tentative avec un backoff exponentiel.
Vous pouvez implémenter un mécanisme de nouvelle tentative au sein de l'interface de secours ou au sein du composant qui a rencontré l'erreur. Envisagez d'utiliser des bibliothèques comme axios-retry ou d'implémenter votre propre logique de nouvelle tentative en utilisant setTimeout.
Exemple (nouvelle tentative de base) :
function RetryComponent({ onRetry }) {
return <button onClick={onRetry}>Réessayer</button>;
}
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, info) {
console.error("Une erreur a été interceptée : ", error, info.componentStack);
}
handleRetry = () => {
this.setState({ hasError: false, error: null }, () => {
//Forcer un nouveau rendu du composant en mettant à jour l'état
this.forceUpdate();
});
};
render() {
if (this.state.hasError) {
return (
<div>
<h1>Quelque chose s'est mal passé.</h1>
<p>{this.state.error?.message}</p>
<RetryComponent onRetry={this.handleRetry} />
</div>
);
}
return this.props.children;
}
}
Bonnes Pratiques pour l'Utilisation des Périphériques de Gestion d'Erreurs
- Encapsuler des Itinéraires Complets : Pour les itinéraires de haut niveau, envisagez d'encapsuler l'itinéraire entier avec un périphérique de gestion d'erreurs pour intercepter toute erreur inattendue qui pourrait survenir. Cela fournit un filet de sécurité et empêche toute l'application de planter.
- Encapsuler les Sections Critiques : Identifiez les sections les plus critiques de votre application (par exemple, le processus de paiement sur un site de commerce électronique) et encapsulez-les avec des périphériques de gestion d'erreurs pour vous assurer qu'elles sont résilientes aux erreurs.
- N'abusez pas des Périphériques de Gestion d'Erreurs : Évitez d'encapsuler chaque composant avec un périphérique de gestion d'erreurs. Cela peut ajouter une surcharge inutile et rendre votre code plus difficile à lire. Concentrez-vous sur l'encapsulation des composants susceptibles d'échouer ou qui sont essentiels à l'expérience utilisateur.
- Fournir des Interfaces de Secours Informatives : L'interface de secours doit fournir des informations claires et utiles à l'utilisateur sur ce qui n'a pas fonctionné et ce qu'il peut faire pour résoudre le problème. Évitez d'afficher des messages d'erreur génériques qui ne fournissent aucun contexte.
- Consigner les Erreurs de Manière Approfondie : Assurez-vous de consigner toutes les erreurs interceptées par les périphériques de gestion d'erreurs dans un service externe de suivi des erreurs. Cela vous aidera à identifier et à corriger rapidement les problèmes sous-jacents.
- Testez Vos Périphériques de Gestion d'Erreurs : Rédigez des tests unitaires et des tests d'intégration pour vous assurer que vos périphériques de gestion d'erreurs fonctionnent correctement et qu'ils interceptent les erreurs attendues. Simulez des conditions d'erreur et vérifiez que l'interface de secours s'affiche correctement.
- Envisagez une Gestion Globale des Erreurs : Bien que les périphériques de gestion d'erreurs soient excellents pour gérer les erreurs au sein des composants React, vous devriez également envisager d'implémenter une gestion globale des erreurs pour intercepter les erreurs qui se produisent en dehors de l'arborescence React (par exemple, les rejets de promesses non gérés).
Considérations Globales et Sensibilité Culturelle
Lors de la conception d'arbres de périphériques de gestion d'erreurs pour un public mondial, il est essentiel de prendre en compte la sensibilité culturelle et la localisation :
- Localisation : Assurez-vous que vos interfaces de secours sont correctement localisées pour différentes langues et régions. Utilisez une bibliothèque de localisation comme
i18nextoureact-intlpour traduire les messages d'erreur et autres textes. - Contexte Culturel : Soyez conscient des différences culturelles lors de la conception de vos interfaces de secours. Évitez d'utiliser des images ou des symboles qui pourraient être offensants ou inappropriés dans certaines cultures. Par exemple, un geste de la main considéré comme positif dans une culture peut être offensant dans une autre.
- Fuseaux Horaires : Si vos messages d'erreur incluent des horodatages ou d'autres informations liées au temps, assurez-vous de les afficher dans le fuseau horaire local de l'utilisateur.
- Devises : Si vos messages d'erreur impliquent des valeurs monétaires, affichez-les dans la devise locale de l'utilisateur.
- Accessibilité : Assurez-vous que vos interfaces de secours sont accessibles aux utilisateurs handicapés. Utilisez les attributs ARIA appropriés et suivez les directives d'accessibilité pour rendre votre application utilisable par tous.
- Consentement au Rapport d'Erreurs : Soyez transparent sur le rapport d'erreurs. Offrez aux utilisateurs la possibilité d'accepter ou de refuser l'envoi de rapports d'erreurs à vos serveurs. Assurez-vous de la conformité avec les réglementations sur la confidentialité comme le RGPD et le CCPA.
Exemple (Localisation avec `i18next`) :
// i18n.js (configuration i18next)
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import en from './locales/en/translation.json';
import fr from './locales/fr/translation.json';
i18n
.use(initReactI18next) // transmet i18n Ă react-i18next
.init({
resources: {
en: { translation: en },
fr: { translation: fr },
},
lng: 'en', // langue par défaut
fallbackLng: 'en',
interpolation: {
escapeValue: false, // react protège déjà contre les attaques XSS
},
});
export default i18n;
// ErrorBoundary.js
import { useTranslation } from 'react-i18next';
function ErrorBoundary(props) {
const { t } = useTranslation();
// ...
render() {
if (this.state.hasError) {
return <h1>{t('error.somethingWentWrong')}</h1>;
}
return this.props.children;
}
}
Conclusion
Les arbres de périphériques de gestion d'erreurs React sont un outil puissant pour construire des applications robustes et résilientes. En plaçant stratégiquement des périphériques de gestion d'erreurs à différents niveaux de votre hiérarchie de composants, vous pouvez isoler les défaillances, fournir des interfaces de secours spécifiques au contexte et améliorer l'expérience utilisateur globale. N'oubliez pas de gérer les erreurs dans les gestionnaires d'événements et les opérations asynchrones en utilisant des blocs try...catch. En suivant les bonnes pratiques et en tenant compte des facteurs globaux et culturels, vous pouvez créer des applications à la fois fiables et conviviales pour un public diversifié.
En mettant en œuvre un arbre de périphériques de gestion d'erreurs bien conçu et en prêtant attention aux détails, vous pouvez améliorer considérablement la fiabilité et l'expérience utilisateur de vos applications React, quel que soit l'endroit où se trouvent vos utilisateurs.